// 常量，意为不可变的量，一般为全大写，多个单词之间用下划线连接
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

class MyPromise {

  // 为什么选择私有实例属性？
  // 1. 选择实例属性的原因是：多个实例之间的状态不能互相影响
  // 2. 选择私有的原因是：要控制状态只能在内部访问，否则外部可以随意修改
  #PromiseState = PENDING

  #PromiseResult = undefined

  // 用来保存一对对的回调（包含成功和失败）
  // 由于可能有多对回调，所以要用一个数组存，格式形如：[ { onFulfilled, onRejected } ]
  #handlers = []

  // then方法是否调用过，默认没调用过
  #thenCalled = false

  // executor - 执行器函数，是同步执行的
  constructor(executor) {

    const resolve = (value) => {

      if (value instanceof MyPromise) {
        // 如果接收的value是一个Promise，那么，当前Promise到底变成功还是失败，取决于接收的这个Promise的状态
        value.then((value) => {
          this.#changeState(FULFILLED, value)
        }, (reason) => {
          this.#changeState(REJECTED, reason)
        })

        // 如下代码 等价于 上面的代码
        // value.then(
        //   this.#changeState.bind(this, FULFILLED), 
        //   this.#changeState.bind(this, REJECTED)
        // )

      } else {
        // 如果接收的value不是一个Promise，就可以直接变成成功
        this.#changeState(FULFILLED, value)
      }
    }

    const reject = (reason) => {
      // 就算reason是Promise，也一视同仁，直接作为结果保存
      this.#changeState(REJECTED, reason)
    }

    try {
      executor(resolve, reject)
    } catch(err) {
      // 如果执行器函数在执行过程中出现异常，则直接导致当前Promise状态变为失败
      reject(err)
    }
  }

  #changeState(state, result) {
    // 状态一旦改变就不能再变
    if (this.#PromiseState !== PENDING) return
    this.#PromiseState = state
    this.#PromiseResult = result
    // 运行回调函数需要放在此，因为状态只能改变一次，所以回调也只能运行一次
    this.#runHandlers()

    // 异步检查是否绑定了回调
    queueMicrotask(() => {
      // 等同步代码执行完后
      // 如果发现还没有绑定回调，并且当前的状态为rejected，则需要将异常抛出
      if (!this.#thenCalled && this.#PromiseState === REJECTED) {
        throw result
      }
    })
  }

  then(onFulfilled, onRejected) {

    this.#thenCalled = true

    // 因为外界在调用的时候，可以不传，所以，需要做默认值处理
    // 只要传的不是函数，就当没传处理，走默认逻辑

    if (typeof onFulfilled !== 'function') {
      // 接到值后，继续返回，传给后面的.then的成功回调
      onFulfilled = value => value
    }

    if (typeof onRejected !== 'function') {
      // 接到原因后，继续抛出原因，交给后面的.then的错误回调处理
      onRejected = reason => { throw reason }
    }

    // 返回一个新的MyPromise实例，为了支持链式调用
    return new MyPromise((resolve, reject) => {
      // 这里的resolve和reject是用来改变这个返回出去的新Promise实例的状态的

      if (this.#PromiseState === FULFILLED) {
        // 立即执行成功的回调
        this.#runHandler(onFulfilled, resolve, reject)
      } else if (this.#PromiseState === REJECTED) {
        // 立即执行失败的回调
        this.#runHandler(onRejected, resolve, reject)
      } else {
        // 不能立即执行回调，要将回调保存起来
        this.#handlers.push({ 
          onFulfilled, 
          onRejected, 
          // 新Promise的resolve和reject也要保存起来，因为当前Promise的状态都没变，新Promise的状态就需要等待当前Promise的状态改变后才能变
          newPromiseResolve: resolve, 
          newPromiseReject: reject 
        })
      }

    })
  }

  catch(onRejected) {
    return this.then(undefined, onRejected)
  }

  finally(onFinally) {
    return this.then(() => {
      onFinally()
    }, () => {
      onFinally()
    })
  }

  #runHandlers() {

    for (const handler of this.#handlers) {
      const {
        onFulfilled,
        onRejected,
        newPromiseResolve,
        newPromiseReject
      } = handler
      
      if (this.#PromiseState === FULFILLED) {
        this.#runHandler(onFulfilled, newPromiseResolve, newPromiseReject)
      } else {
        this.#runHandler(onRejected, newPromiseResolve, newPromiseReject)
      }
    }

    // this.#handlers.forEach((handler) => {
    //   if (this.#PromiseState === FULFILLED) {
    //     this.#runHandler(handler.onFulfilled, handler.newPromiseResolve, handler.newPromiseReject)
    //   } else {
    //     this.#runHandler(handler.onRejected, handler.newPromiseResolve, handler.newPromiseReject)
    //   }
    // })
  }

  #runHandler(cb, newPromiseResolve, newPromiseReject) {
    // 将回调，插入到异步回调队列中
    queueMicrotask(() => {
      try {
        const returnValue = cb(this.#PromiseResult)
        // 什么时候调用新Promise的resolve呢？

        // 能走到这里，说明上一个回调没出现异常

        if (returnValue instanceof MyPromise) {
          // 如果返回值是一个Promise，那么下一个到底进成功还是失败，取决于，这个返回的Promise成功了还是失败了

          // returnValue.then((value) => {
          //   newPromiseResolve(value)
          // }, (reason) => {
          //   newPromiseReject(reason)
          // })

          // 当returnValue状态改变时，就会调用对应的回调，调用回调时，就会传实参（结果）
          returnValue.then(newPromiseResolve, newPromiseReject)

        } else {
          // 如果返回值不是一个Promise，那么下一个就进入成功的回调
          // 下一次value拿到的是上一次回调的返回值
          newPromiseResolve(returnValue)
        }

      } catch(err) {
        // 只要在回调执行的过程中出现异常，下一个就应该进入失败的回调
        newPromiseReject(err)
      }

    })
  }

  // 快速创建一个状态为fulfilled的Promise
  static resolve(value) {
    if (value instanceof MyPromise) return value
    return new MyPromise((resolve, reject) => {
      resolve(value)
    })
  }
  
  // 快速创建一个状态为rejected的Promise
  static reject(reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason)
    })
  }

  static resolveDelay(value, ms) {
    return new MyPromise((resolve, reject) => {
      setTimeout(resolve, ms, value)
    })
  }

  static rejectDelay(reason, ms) {
    return new MyPromise((resolve, reject) => {
      setTimeout(reject, ms, reason)
    })
  }

  static race(promises) {
    return new MyPromise((resolve, reject) => {
      for (const p of promises) {
        MyPromise.resolve(p).then(resolve, reject)
      }
    })
  }

  static all(promises) {
    // 难点：
    // 1. 如何保证值的顺序和原数组一致？
    // 2. 如何知道都跑完了？
    return new MyPromise((resolve, reject) => {
      const values = []
      // 添加的个数
      let addCount = 0
      // i 是 p 在 promises 数组中的索引
      for (const [i, p] of promises.entries()) {
        MyPromise.resolve(p).then((value) => {

          // 存值的时候，不能直接push，因为会追加到最后，先跑完的先追加，顺序就乱了
          // values.push(value)
          values[i] = value

          // 存一个值，计数器+1
          addCount++

          // 如何知道都跑完了呢？
          if (addCount === promises.length) {
            resolve(values)
          }

          // #region BUG
          // 不能直接用values.length去做比较，因为我们上面是直接用索引赋值的，可能跨过了一些索引，但是length也把空位算进去了，所以需要先过滤出有值的元素，再判断长度，
          // BUG：过滤的逻辑不严谨，因为这个值可能是任何值，甚至可能值就是一个undefined或者其他的假值，还是用计数比较靠谱
          // if (values.filter(v => v).length === promises.length) {
          //   // 如果存的值的长度 等于 传进来的数组的长度，说明都搞完了
          //   resolve(values)
          // }
          // #endregion

        }, (reason) => {
          // 只要进入失败，就直接失败
          reject(reason)
        })
      }
    })
  }

  static allSettled(promises) {
    return new MyPromise((resolve, reject) => {
      const values = []
      let addCount = 0
      for (const [i, p] of promises.entries()) {
        MyPromise.resolve(p).then((value) => {
          values[i] = { status: FULFILLED, value }
        }, (reason) => {
          values[i] = { status: REJECTED, reason }
        }).finally(() => {
          addCount++
          if (addCount === promises.length) {
            resolve(values)
          }
        })
      }
    })
  }

  static any(promises) {
    return new MyPromise((resolve, reject) => {
      const errors = []
      let errorCount = 0
      for (const [i, p] of promises.entries()) {
        p.then((value) => {
          resolve(value)
        }, (reason) => {
          errors[i] = reason
          errorCount++
          if (errorCount === promises.length) {
            reject(new AggregateError(errors, 'All promises were rejected'))
          }
        })
      }
    })
  }

  // 尝试将func的返回值当作Promise去处理
  static try(func, ...args) {
    return new MyPromise((resolve, reject) => {
      const returnValue = func(...args)
      resolve(returnValue)
    })
  }

}